package cc.owoo.godpen.file;

import cc.owoo.godpen.thread.CacheThreadPool;
import cc.owoo.godpen.util.Path;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;

/**
 * 更多高级功能的文件类，有多线程删除、拷贝、移动等功能，还有快速读取文本、写入文本等实用函数
 * Created by nimensei
 * 2022-02-27 上午 11:52
 **/
public class SuperFile extends File {
    private static final int DEFAULT_THREAD_COUNT = 4;// 默认线程数

    public SuperFile(String pathname) {
        super(pathname);
    }

    public SuperFile(String parent, String child) {
        super(parent, child);
    }

    public SuperFile(File parent, String child) {
        super(parent, child);
    }

    public SuperFile(URI uri) {
        super(uri);
    }

    /**
     * 遍历所有文件
     *
     * @param file 根目录
     * @return 结果集合
     */
    public static List<FilePath> getAllFile(File file) {
        return getAllFile(file, null);
    }

    /**
     * 遍历所有文件
     *
     * @param file 根目录
     * @param res  结果集合，可以为空
     * @return 结果集合
     */
    public static List<FilePath> getAllFile(File file, List<FilePath> res) {
        if (res == null)
            res = new LinkedList<>();
        if (!file.exists())
            return res;
        getAllFile(file, new Path(), res);
        return res;
    }

    /**
     * 执行文件遍历
     *
     * @param file 根目录
     * @param res  结果集合
     */
    private static void getAllFile(File file, Path relative, List<FilePath> res) {
        relative.addLastName(file.getName());
        if (file.isFile()) {
            res.add(new FilePath(file, relative));
            return;
        }
        String absolutePath = file.getAbsolutePath() + "/";
        String[] list = file.list();
        if (list == null)
            return;
        for (String s : list)
            getAllFile(new File(absolutePath + s), relative.copy(), res);
    }

    /**
     * 执行多线程删除
     *
     * @return 是否完全删除成功
     */
    private static boolean executeThreadDelete(int threadCount, LinkedList<File> list) {
        CacheThreadPool task = new CacheThreadPool(threadCount);
        CountDownLatch count = new CountDownLatch(list.size());
        boolean[] flag = {true};
        for (File file : list)
            executeThreadDelete(file, task, count, flag);
        try {
            count.await();
        } catch (InterruptedException ignored) {
        }
        return flag[0];
    }

    /**
     * 执行多线程删除
     *
     * @param file  删除的根目录
     * @param task  线程任务
     * @param count 线程阻塞
     * @param flag  是否完全删除成功
     */
    private static void executeThreadDelete(File file, CacheThreadPool task, CountDownLatch count, boolean[] flag) {
        if (!flag[0]) {
            count.countDown();
            return;
        }
        con:
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files == null || files.length == 0)
                break con;
            CountDownLatch child = new CountDownLatch(files.length);
            for (File f : files)
                executeThreadDelete(f, task, child, flag);
            try {
                child.await();
            } catch (InterruptedException ignored) {
            }
        }
        task.execute(() -> {
            if (flag[0] && !file.delete())
                flag[0] = false;
            count.countDown();
        });
    }

    /**
     * 多线程删除多个文件夹或文件
     *
     * @param paths 路径数组
     * @return 是否完全删除成功
     */
    public static boolean threadDeleteList(String... paths) {
        return threadDeleteList(DEFAULT_THREAD_COUNT, paths);
    }

    /**
     * 多线程删除多个文件夹或文件
     *
     * @param threadCount 线程数量
     * @param paths       路径数组
     * @return 是否完全删除成功
     */
    public static boolean threadDeleteList(int threadCount, String... paths) {
        if (paths == null || paths.length == 0)
            throw new NullPointerException("需要删除文件不能为空");
        File[] files = new File[paths.length];
        for (int i = 0; i < files.length; i++) {
            if (paths[i] == null)
                throw new NullPointerException("需要删除的路径不能为空：index = " + i);
            files[i] = new File(paths[i]);
        }
        return threadDeleteList(threadCount, files);
    }

    /**
     * 多线程删除多个文件夹或文件
     *
     * @param threadCount 线程数量
     * @param files       文件夹数组
     * @return 是否完全删除成功
     */
    public static boolean threadDeleteList(int threadCount, File... files) {
        if (files == null || files.length == 0)
            throw new NullPointerException("需要删除文件不能为空");
        DeleteFile[] paths = new DeleteFile[files.length];
        for (int i = 0; i < paths.length; i++) {
            if (files[i] == null)
                throw new NullPointerException("需要删除文件不能为空：index = " + i);
            String path = files[i].getAbsolutePath();
            paths[i] = new DeleteFile(new Path(path), files[i]);
        }
        Arrays.sort(paths, (a, b) -> a.path.size() - b.path.size());
        LinkedList<File> list = new LinkedList<>();
        PathMap map = new PathMap();
        for (var item : paths)
            if (map.addIfParent(item.path))
                list.add(item.file);
        return executeThreadDelete(threadCount, list);
    }

    /**
     * 需要删除的文件
     */
    private record DeleteFile(Path path, File file) {
    }

    /**
     * 多线程删除
     *
     * @return 是否完全删除成功
     */
    public boolean executeThreadDelete() {
        return executeThreadDelete(DEFAULT_THREAD_COUNT);
    }

    /**
     * 多线程删除
     *
     * @param threadCount 线程数量
     * @return 是否完全删除成功
     */
    public boolean executeThreadDelete(int threadCount) {
        if (threadCount <= 0)
            throw new IllegalArgumentException("线程数不能小于1");
        if (!this.exists())
            return true;
        if (this.isFile())
            return super.delete();
        CacheThreadPool task = new CacheThreadPool(threadCount);
        CountDownLatch count = new CountDownLatch(1);
        boolean[] flag = {true};
        executeThreadDelete(this, task, count, flag);
        try {
            count.await();
        } catch (InterruptedException ignored) {
        }
        return flag[0];
    }

    /**
     * 文件或文件夹拷贝
     *
     * @param source 源文件
     * @param target 目标目录
     * @return 拷贝的与文件夹数量
     */
    public static int copy(File source, File target) throws IOException {
        return copy(source, target, false);
    }

    /**
     * 覆盖拷贝，如果需要拷贝的文件已存在，则进行覆盖
     *
     * @param source 源文件
     * @param target 目标目录
     * @return 拷贝的与文件夹数量
     */
    public static int coverCopy(File source, File target) throws IOException {
        return copy(source, target, true);
    }

    /**
     * 文件或文件夹拷贝
     *
     * @param source 源文件
     * @param target 目标目录
     * @param cover  是否覆盖已存在的文件
     * @return 拷贝的与文件夹数量
     */
    public static int copy(File source, File target, boolean cover) throws IOException {
        if (!source.exists())
            return 0;
        if (source.isFile()) {
            copyFile(source, target, cover);
            return 1;
        }
        if (!target.exists() && !target.mkdirs())
            throw new RuntimeException("文件夹创建失败");
        return copyFolder(source, target, cover);
    }

    /**
     * 拷贝文件夹
     *
     * @param source 源文件
     * @param target 目标目录
     */
    private static void copyFile(File source, File target, boolean cover) throws IOException {
        FileInputStream input = new FileInputStream(source);
        FileOutputStream output = new FileOutputStream(target);
        byte[] bytes = new byte[1024];
        int len;
        while ((len = input.read(bytes)) != -1)
            output.write(bytes, 0, len);
        input.close();
        output.close();
    }

    /**
     * 拷贝文件夹
     *
     * @param source 源文件
     * @param target 目标目录
     * @return 拷贝的与文件夹数量
     */
    private static int copyFolder(File source, File target, boolean cover) throws IOException {
        String[] list = Objects.requireNonNull(source.list());
        int count = list.length;
        for (String s : list) {
            File a = new File(source.getAbsolutePath() + "/" + s);
            File b = new File(target.getAbsolutePath() + "/" + s);
            if (a.isFile())
                copyFile(a, b, cover);
            else {
                if (!b.exists() && !b.mkdir())
                    throw new RuntimeException("文件夹创建失败");
                count += copyFolder(a, b, cover);
            }
        }
        return count;
    }

    /**
     * 获取后缀，如果没有后缀，则返回默认null
     *
     * @return 后缀
     */
    public String getPostfix() {
        return getPostfix(null);
    }

    /**
     * 获取后缀
     *
     * @param defaultPostfix 如果没有后缀，则返回默认后缀
     * @return 后缀
     */
    public String getPostfix(String defaultPostfix) {
        String name = getName();
        int index = name.lastIndexOf('.');
        if (index == -1)
            return defaultPostfix;
        return name.substring(index);
    }

    /**
     * 将数据写入到文件
     *
     * @param file  文件
     * @param bytes 数据
     * @throws IOException 处理异常
     */
    public static boolean write(File file, byte[] bytes) throws IOException {
        if (file == null || bytes == null)
            return false;
        FileOutputStream output = new FileOutputStream(file);
        output.write(bytes);
        output.close();
        return true;
    }

    /**
     * 读取文件并将其转换为字符串，默认使用UTF-8解码
     *
     * @param file 文件对象
     * @return 字符串
     * @throws IOException 处理异常
     */
    public static String readText(File file) throws IOException {
        return readText(file, StandardCharsets.UTF_8);
    }

    /**
     * 读取文件并将其转换为字符串
     *
     * @param file   文件对象
     * @param encode 字符串编码
     * @return 字符串
     * @throws IOException 处理异常
     */
    public static String readText(File file, Charset encode) throws IOException {
        byte[] content = read(file);
        if (content == null)
            return null;
        return encode == null ? new String(content) : new String(content, encode);
    }

    /**
     * 读取本地文件中的数据
     *
     * @param file 文件对象
     * @return byte数组
     * @throws IOException 处理异常
     */
    public static byte[] read(File file) throws IOException {
        if (file == null || !file.isFile())
            return null;
        FileInputStream inputStream = new FileInputStream(file);
        byte[] bytes = inputStream.readAllBytes();
        inputStream.close();
        return bytes;
    }
}
